//
//  ChainSerialTasks.swift
//  
//
//  Created by tangbl93 on 2021/3/3.
//

import Foundation
import Promises

// MARK: ChainSerialTasks
public final class ChainSerialTasks {

    // shared
    public static var shared: ChainSerialTasks = ChainSerialTasks()

    // whether chain started
    public var chainStarted:Bool {
        set {
            // cant set twice
            if _chainStarted {
                #if DEBUG
                fatalError()
                #else
                return
                #endif
            }
            _chainStarted = true
            chainSerialTask.fulfill(())
            taskLog("chainSerialTask started")
        }
        get {
            return _chainStarted
        }
    }
    // whether chain started
    private var _chainStarted: Bool = false
    
    // enableLogging
    public var enableLogging: Bool = true
    
    // dispatchQueue
    public var dispatchQueue: DispatchQueue = DispatchQueue.global()
    
    // the chain
    private var chainSerialTask: Promise<Void>
    
    // runningTasks
    private var runingTasks: [String: Promise<Void>] = [:]
    public var runningTaskKeys: [String] { return Array(runingTasks.keys) }
    
    // init
    init() {
        chainSerialTask = Promise.pending()
    }
}

// MARK: Action
public extension ChainSerialTasks {
    
    // scheduled task
    func scheduled(key:String, action:@escaping ChainSerialTaskItem.Action) {
        taskLog("chainSerialTask scheduled:'\(key)'")
        
        // Ensure that only one task is running at the same time
        if runingTasks.keys.contains(key) {
            return
        }
        
        // scheduled task
        chainSerialTask = chainSerialTask.then { _ -> Promise<Void> in
            let pending = ChainSerialTaskItem.pending(key: key)
            action(pending)
            return pending.promise
        }.recover({ (error) -> Promise<Void> in
            Self.taskLog("chainSerialTask recover:\(error)")
            
            let pending: Promise<Void> = Promise.pending()
            pending.fulfill(())
            return pending
        }).always { [weak self] in
            self?.remove(key)
        }
        
        // store task
        runingTasks[key] = chainSerialTask
    }
    
    // remove task
    func remove(_ key: String) {
        taskLog("chainSerialTask remove key:'\(key)'")
        
        if runingTasks.keys.contains(key) {
            runingTasks.removeValue(forKey: key)
        }
    }

    // remove all task
    func removeAll() {
        taskLog("chainSerialTask removeAll")
        
        runingTasks.removeAll()
    }
}

// MAKR: Shared
public extension ChainSerialTasks {

    // scheduled task
    static func scheduled(key:String, action:@escaping ChainSerialTaskItem.Action) {
        shared.scheduled(key: key, action: action)
    }

    // remove task
    static func remove(_ key: String) {
        shared.remove(key)
    }

    // remove all task
    static func removeAll() {
        shared.removeAll()
    }
}

// MARK: Logging
extension ChainSerialTasks {
    
    private func taskLog(_ log: String) {
        if enableLogging { YXNSLog(log) }
    }
    
    private static func taskLog(_ log: String) {
        shared.taskLog(log)
    }
}
